package cn.chengpengper.nacos.client.properties;

import cn.chengpengper.nacos.client.contant.Constants;
import cn.chengpengper.nacos.client.convert.CompositeConverter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Slf4j
public class SearchableProperties implements NacosClientProperties {
    /**
     * JVM 属性
     */
    private static final JvmArgsPropertySource JVM_ARGS_PROPERTY_SOURCE = new JvmArgsPropertySource();

    /**
     * 系统环境变量属性
     */
    private static final SystemEnvPropertySource SYSTEM_ENV_PROPERTY_SOURCE = new SystemEnvPropertySource();

    private final PropertiesPropertySource propertiesPropertySource;

    /**
     * 此成员变量存放了所有的属性源对象，查找配置信息时从此成员变量中查找
     */
    private final List<AbstractPropertySource> propertySources;

    /**
     * 单例模式
     */
    static final SearchableProperties INSTANCE = new SearchableProperties();

    /**
     * 暴露单例对象
     */
    NacosClientProperties PROTOTYPE = SearchableProperties.INSTANCE;

    /**
     * 搜索配置信息的顺序
     */
    private static final List<SourceType> SEARCH_ORDER;

    private static final CompositeConverter CONVERTER = new CompositeConverter();

    static {
        // 默认配置信息搜索顺序：PROPERTIES -> JVM -> ENV
        List<SourceType> initOrder = Arrays.asList(SourceType.PROPERTIES, SourceType.JVM, SourceType.ENV);

        String firstEnv = JVM_ARGS_PROPERTY_SOURCE.getProperty(Constants.SysEnv.NACOS_ENV_FIRST);
        if (StringUtils.isBlank(firstEnv)) {
            firstEnv = SYSTEM_ENV_PROPERTY_SOURCE.getProperty(Constants.SysEnv.NACOS_ENV_FIRST);
        }
        if (StringUtils.isNotBlank(firstEnv)) {
            try {
                SourceType sourceType = SourceType.valueOf(firstEnv.toUpperCase());
                if (!sourceType.equals(SourceType.PROPERTIES)) {
                    int index = initOrder.indexOf(sourceType);
                    SourceType replacedSourceType = initOrder.set(0, sourceType);
                    initOrder.set(index, replacedSourceType);
                }
            } catch (Exception e) {
                log.warn("first source type parse error, it will be used default order!", e);
            }
        }

        SEARCH_ORDER = initOrder;
        StringBuilder orderInfo = new StringBuilder("properties search order:");
        for (int i = 0; i < SEARCH_ORDER.size(); i++) {
            orderInfo.append(SEARCH_ORDER.get(i).toString());
            if (i < SEARCH_ORDER.size() - 1) {
                orderInfo.append("->");
            }
        }
        log.debug(orderInfo.toString());
    }

    private SearchableProperties() {
        this(new PropertiesPropertySource());
    }

    private SearchableProperties(PropertiesPropertySource propertiesPropertySource) {
        this.propertiesPropertySource = propertiesPropertySource;
        this.propertySources = build(propertiesPropertySource, JVM_ARGS_PROPERTY_SOURCE, SYSTEM_ENV_PROPERTY_SOURCE);
    }

    private List<AbstractPropertySource> build(AbstractPropertySource... propertySources) {
        Map<SourceType, AbstractPropertySource> sourceMap = Arrays.stream(propertySources).collect(Collectors.toMap(AbstractPropertySource::getType, Function.identity()));
        return SEARCH_ORDER.stream().map(sourceMap::get).collect(Collectors.toList());
    }

    /**
     * 根据Key从属性源集合中寻找对应的value
     *
     * @param key        key
     * @param targetType value的类型
     * @param <T>        泛型
     * @return 值
     */
    private <T> Optional<T> search(String key, Class<T> targetType) {
        if (Objects.isNull(targetType)) {
            throw new IllegalArgumentException("target type must not be null!");
        }

        for (AbstractPropertySource propertySource : propertySources) {
            String value = propertySource.getProperty(key);
            if (StringUtils.isNotBlank(value)) {
                if (String.class.isAssignableFrom(targetType)) {
                    try {
                        return (Optional<T>) Optional.of(value);
                    } catch (Exception e) {
                        log.error("target type convert error", e);
                        return Optional.empty();
                    }
                }
                return Optional.ofNullable(CONVERTER.convert(value, targetType));
            }
        }
        return Optional.empty();
    }

    @Override
    public NacosClientProperties derive() {
        return new SearchableProperties(new PropertiesPropertySource(this.propertiesPropertySource));
    }

    @Override
    public NacosClientProperties derive(Properties properties) {
        NacosClientProperties nacosClientProperties = this.derive();
        nacosClientProperties.addProperties(properties);
        return nacosClientProperties;
    }

    @Override
    public void addProperties(Properties properties) {
        propertiesPropertySource.addProperties(properties);
    }
}
